package tv.geir.slackspace.matching;

import static tv.geir.slackspace.matching.Operator.EQUALS;

import java.io.Serializable;

import org.apache.commons.lang.builder.ToStringBuilder;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import tv.geir.slackspace.common.ObjectSerializer;
import tv.geir.slackspace.common.SlackSpaceException;

/**
 * Represents a property or a public field
 * 
 * @author Geir Gullestad Pettersen
 *
 */
public class TemplateProperty implements Serializable {
	
	private static final long serialVersionUID = 1L;

	private static transient Log log = LogFactory.getLog( TemplateProperty.class );
	
	private transient static ObjectSerializer objectSerializer = new ObjectSerializer();
	
	private boolean defaultPrimitiveIsWildcard = true;
	
	private Operator operator = EQUALS;
	
	/**
	 * Property's name, may be <code>null</code>.
	 */
	public String name;

	/**
	 * Property's serialized value, may be <code>null</code>.
	 */
	public byte[] value;

	/**
	 * Property's type (e.g. "java.lang.String"), may be <code>null</code>.
	 */
	public String type;
			
	private transient Object valueInstance = null;

	/**
	 * Constructs a TemplateProperty
	 */
	public TemplateProperty( ) {}
	
	
	/**
	 * Constructs a TemplateProperty with given name, type and value.
	 * null-values are permitted (used as wildcards in the matching algorithm)
	 * @param name. Default operator (EQUALS) is used.
	 * @param type
	 * @param value
	 */
	public TemplateProperty( String name, String type, Object value ) {
		this.name = name;
		this.type = type;
		setValue( value );
	}
	
	public String toString() {
		return new ToStringBuilder(this)
        .append("name", name)
        .append("type", type)
        .append("operator", operator)
        .append("value", getValue())
        .toString();
	}

	/**
	 * Sets the value of this property. <code>o</code> will be serialized before
	 * it is stored in the property, so if it does not implement
	 * <code>java.io.Serializable</code> 
	 * @param o
	 */
	public void setValue(Object o) {
		// object is serialized to avoid classcastexception on server
		try {
			value = objectSerializer.serialize(o);
			valueInstance = o ;
		}catch (SlackSpaceException sse) {
            /* serialization failed. ignore the error but log it.
             * this may or may not cause matching problems.
             */
			log.warn("Error serializing template property \'" + name
						+ "\': " + sse.getMessage(), sse);
		
		}
	}

	/**
	 * Gets the value of this property. <code>null</code> is returned if
	 * deserialization fails. Deserialization can fail if a class referenced or used
	 * by <code>value</code> is not available (a <code>ClassNotFoundException</code>
	 * is thrown), however this exception is caught and null is returned instead.
	 * <p>
	 * If you need to know wheter a returned <code>null</code> is due to failed deserialization
	 * or if it is an actual null-value being returned, you can check the contents of
	 * the public field <code>value</code>.
	 * The <code>getValue()</code>-method will only return null if <code>value</code>
	 * is a 0-lenght array or <code>null</code>.
	 *  
	 * @return value or <code>null</code> if operation fails.
	 */
	public Object getValue() {
		try {
			/*
			 * If not yet deserialized, try to deserialize value 
			 */

			if( valueInstance == null &&
				value != null &&
				value.length > 0) 
			{
				valueInstance = objectSerializer.deserialize(value);
			}
		} catch (SlackSpaceException sse) {
			// this error typically occurs when a template contains values that are instances 
			// of classes not in server's classpath. 
			log.warn("Unable to deserialize property [name=" + name + "] type=[" + type + "]: "
					+ sse.getMessage(), sse);
		}
		return valueInstance;
	}


	public boolean isDefaultPrimitiveIsWildcard() {
		return defaultPrimitiveIsWildcard;
	}


	public void setDefaultPrimitiveIsWildcard(
			boolean primitiveDefaultValueWildcard) {
		this.defaultPrimitiveIsWildcard = primitiveDefaultValueWildcard;
	}
	
	

	/**
	 * @return Returns the operator.
	 */
	public Operator getOperator() {
		return operator;
	}


	/**
	 * @param operand The operator to set.
	 */
	public void setOperator(Operator operand) {
		this.operator = operand;
	}
}

